#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>

template <class T > class MemoryPool;
template <class T > class MemoryChunk;

template <class T > class MemoryPool
{
	public:
		MemoryPool( uint32_t initial_allocation );
		MemoryPool( uint32_t initial_allocation, uint32_t array_size );
		~MemoryPool(void);

		inline uint64_t getUsedMem() const { return total_mem_used; }
		inline uint64_t getFreeMem() const { return total_mem_free; }
		inline uint64_t getUsedPackets() const { return total_blocks - free_blocks; }
		inline uint64_t getFreePackets() const { return free_blocks; }

		T *create();
		void destroy( T *t );
		void print() { printf( "Total Mem Used: %llubytes\nTotal Free Mem: %llubytes\nFree Blocks: %llu\nTotal Blocks: %llu\n" , total_mem_used, total_mem_free, free_blocks, total_blocks ); }
	private:
		void allocate_chunks();
		void allocate_chunks( uint32_t array_size );
		MemoryChunk< T > *head;
		MemoryChunk< T > *tail;
		MemoryChunk< T > *cursor;
		uint64_t total_mem_free;
		uint64_t total_mem_used;
		uint64_t free_blocks;
		uint64_t total_blocks;
};

template <class T > MemoryPool< T >::MemoryPool( uint32_t initial_allocation ) : total_blocks( initial_allocation ), free_blocks( initial_allocation ), total_mem_used( 0 )
{
	total_mem_free = total_blocks * sizeof( T );

	allocate_chunks();

	cursor = head;
}

template <class T > MemoryPool< T >::MemoryPool( uint32_t initial_allocation, uint32_t array_size ) : total_blocks( initial_allocation ), free_blocks( initial_allocation ), total_mem_used( 0 )
{
	total_mem_free = total_blocks * sizeof( T ) * array_size;

	allocate_chunks( array_size );

	cursor = head;
}

template <class T > MemoryPool< T >::~MemoryPool()
{
	while( head->getNext() != 0 )
	{
		MemoryChunk< T > *t = head;
		head = head->getNext();

		t->setNext( 0 );
		delete t;
		t = 0;
	}

	delete head;
	head = 0;
	tail = 0;
	cursor = 0;
}

template <class T > void MemoryPool< T >::allocate_chunks()
{
	head = new MemoryChunk< T >( this );

	MemoryChunk< T > *cur = head;
	
	for( uint32_t i = 0; i < total_blocks - 1; i++ )
	{
		MemoryChunk< T > *t = new MemoryChunk< T >( this );
		cur->setNext( t );
		cur = cur->getNext();
	}

	tail = cur;
}

template <class T > void MemoryPool< T >::allocate_chunks( uint32_t array_size )
{
	head = new MemoryChunk< T >( this, array_size );

	MemoryChunk< T > *cur = head;
	
	for( uint32_t i = 0; i < total_blocks - 1; i++ )
	{
		MemoryChunk< T > *t = new MemoryChunk< T >( this, array_size );
		cur->setNext( t );
		cur = cur->getNext();
	}

	tail = cur;
}

template <class T > T *MemoryPool< T >::create()
{
	bool isFull = true;

	do
	{
		if( cursor == tail && isFull )
		{
			isFull = false;
			cursor = head;
		}

		while( cursor != tail )
		{
			if( !cursor->allocated() )
			{
				free_blocks--;
				total_mem_free -= cursor->getSize();
				total_mem_used += cursor->getSize();
				cursor->allocated( true );
				return cursor->getPointer();
			}
			else
			{
				cursor = cursor->getNext();
			}
		}

		if( !cursor->allocated() )
		{
			free_blocks--;
			total_mem_free -= cursor->getSize();
			total_mem_used += cursor->getSize();
			cursor->allocated( true );
			return cursor->getPointer();
		}

	}while( isFull );

	return 0;
}

template <class T > void MemoryPool< T >::destroy( T *t )
{
	if( t == 0 )
		return;

	MemoryChunk< T > *cur = head;

	while( cur != 0 )
	{
		if( cur->allocated() )
		{
			if( t == cur->getPointer() )
			{
				cur->allocated( false );
				free_blocks++;
				total_mem_free += cursor->getSize();
				total_mem_used -= cursor->getSize();
				return;
			}
			else
				cur = cur->getNext();
		}
		else
			cur = cur->getNext();
	}
}

template <class T > class MemoryChunk
{
	public:
		MemoryChunk( MemoryPool< T > *pool );
		MemoryChunk( MemoryPool< T > *pool, uint32_t array_count );
		~MemoryChunk(void);

		T *getPointer() { return pointer; }

		void setNext( MemoryChunk< T > *n ) { next = n; }
		MemoryChunk< T > *getNext() { return next; }
		uint64_t getSize() const { return size; }

		void destroy() { pool->destroy( this ); }
		bool allocated() const { return isAllocated; }
		void allocated( bool a ) { isAllocated = a; }
	private:
		T *pointer;
		bool isAllocated;
		bool isArray;
		MemoryChunk< T > *next;
		MemoryPool< T > *pool;
		uint64_t size;
};

template <class T > MemoryChunk< T >::MemoryChunk( MemoryPool< T > *p ) : pool( p ), isAllocated( false ), isArray( false ), next( 0 )
{
	size = sizeof( T );
	pointer = new T();
}

template <class T > MemoryChunk< T >::MemoryChunk( MemoryPool< T > *p, uint32_t array_count ) : pool( p ), isAllocated( false ), isArray( true ), next( 0 )
{
	size = sizeof( T ) * array_count;
	pointer = new T[array_count];
}

template <class T > MemoryChunk< T >::~MemoryChunk()
{
	if( isArray )
		delete [] pointer;
	else
		delete pointer;

	pointer = 0;
}